Skip to content

Bind SCP file timestamps to open descriptor#1037

Open
yosuke-wolfssl wants to merge 1 commit into
wolfSSL:masterfrom
yosuke-wolfssl:fix/f_5114
Open

Bind SCP file timestamps to open descriptor#1037
yosuke-wolfssl wants to merge 1 commit into
wolfSSL:masterfrom
yosuke-wolfssl:fix/f_5114

Conversation

@yosuke-wolfssl

Copy link
Copy Markdown
Contributor

Bind SCP file timestamps to the open descriptor (fix symlink TOCTOU)

Summary

In the SCP receive path, peer-supplied modification/access times were applied with a path-based utimes() after the destination file was closed. On POSIX, utimes() follows symlinks, so a local process running as the authenticated user could replace the just-written file with a symlink in the window between the close and the timestamp update, redirecting the peer-controlled mTime/aTime onto an arbitrary target inode.

This change binds the timestamp update to the open file descriptor so it can no longer be redirected through the path.

Changes

src/wolfscp.c

  • SetTimestampInfo() now takes the open WFILE*. On descriptor-capable platforms it applies the timestamps to the open file:
    • POSIX: futimes(fileno(fp), ...) (via the new WFUTIMES port macro)
    • Windows: _futime(_fileno(fp), ...) instead of re-opening the path
  • Buffered file data is flushed before applying the timestamps, so the subsequent fclose() does not write and overwrite the modification time.
  • The WOLFSSH_SCP_FILE_DONE handler now applies the timestamp before WFCLOSE on descriptor-capable platforms (WOLFSSH_SCP_FD_UTIMES), and keeps the original after-close path-based update on platforms without a descriptor interface — so no port loses its timestamp to the closing flush.
  • Windows branch now null-checks fp, matching the POSIX contract.
  • libc return values (futimes/_futime/utimes) are normalized to WS_FATAL_ERROR on failure, consistent with the function's documented WS_SUCCESS/negative contract.

wolfssh/port.h

  • Added a descriptor-based WFUTIMES(fd, t) macro, defined only when HAVE_FUTIMES is detected. futimes() is a BSD extension (not POSIX), so gating it avoids implicit-declaration breakage on targets that lack it (Solaris, older Android, strict-standard builds); those fall back to the existing path-based WUTIMES.

configure.ac

  • AC_CHECK_FUNCS([... futimes]) so HAVE_FUTIMES is detected per platform.

tests/unit.c

  • Added test_ScpRecvCallback_Timestamp, an end-to-end regression test that drives the default receive callback through a full single-file receive and asserts the written file's mtime/atime equal the peer-supplied values.
    When the descriptor path is compiled in, it also pins the flush-before-set ordering. Lives in the internal-symbol unit test (libwolfssh_test).

Platform behavior

Platform Timestamp update
POSIX with futimes (HAVE_FUTIMES) futimes() on the open fd, before close
Windows _futime() on the open CRT fd, before close
POSIX without futimes path-based utimes() after close
Nucleus / other path-only ports path-based WUTIMES after close (unchanged)
VxWorks / Zephyr / Microchip no-op (unchanged)

Testing

  • tests/unit.test — new ScpRecvCallback_Timestamp passes; full suite green under ASan + UBSan (no leak detection on macOS).
  • Negative control: removing the pre-flush makes the new test fail (fclose clobbers the modification time), confirming it catches the regression; restoring the fix makes it pass.
  • Fallback path: with HAVE_FUTIMES forced off, the build is clean and the path-based after-close update still applies the timestamp correctly.
  • End-to-end: OpenSSH scp -p into the wolfSSH echoserver sink — the destination mtime matches the source; before the fix it was the current time.
  • scripts/scp.test — all cases pass.
  • Production build (--enable-scp, no sanitizers) — clean, no new warnings.

@yosuke-wolfssl yosuke-wolfssl self-assigned this Jun 16, 2026
Copilot AI review requested due to automatic review settings June 16, 2026 06:02

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Hardens the SCP receive path against a symlink TOCTOU by applying peer-supplied timestamps to the still-open destination file descriptor (when supported), instead of applying them later via a path-based utimes().

Changes:

  • Update SCP receive timestamp application to prefer fd-based timestamp setting (futimes / _futime) and flush before applying times to prevent close-time writes from clobbering mtime.
  • Add WFUTIMES port macro gated by HAVE_FUTIMES, and detect futimes in configure.ac.
  • Add an end-to-end unit test asserting received files end up with the peer-supplied mtime/atime.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.

File Description
src/wolfscp.c Applies timestamps via the open file descriptor (when available) with pre-flush ordering to prevent TOCTOU and mtime clobbering.
wolfssh/port.h Introduces WFUTIMES macro gated by HAVE_FUTIMES for portability.
configure.ac Adds futimes feature detection to enable fd-based timestamp updates.
tests/unit.c Adds an end-to-end regression test for SCP receive timestamp behavior.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread configure.ac
Comment thread tests/unit.c Outdated
Comment thread tests/unit.c

@wolfSSL-Fenrir-bot wolfSSL-Fenrir-bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fenrir Automated Review — PR #1037

Scan targets checked: wolfssh-bugs, wolfssh-src

No new issues found in the changed files. ✅

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants